package net.kosev.su.tetris.game;

import java.util.Random;

import net.kosev.su.tetris.Config;
import net.kosev.su.tetris.tetrominoes.BlueTetromino;
import net.kosev.su.tetris.tetrominoes.CyanTetromino;
import net.kosev.su.tetris.tetrominoes.GreenTetromino;
import net.kosev.su.tetris.tetrominoes.OrangeTetromino;
import net.kosev.su.tetris.tetrominoes.PurpleTetromino;
import net.kosev.su.tetris.tetrominoes.RedTetromino;
import net.kosev.su.tetris.tetrominoes.Tetromino;
import net.kosev.su.tetris.tetrominoes.YellowTetromino;

public class Board {
	
	public final static int WIDTH = Integer.parseInt(Config.getInstance().get("board_width"));
	public final static int HEIGHT = Integer.parseInt(Config.getInstance().get("board_height"));
	
	protected Type[][] board = new Type[HEIGHT][WIDTH];
	protected Tetromino currentTetromino = null;
	protected Tetromino nextTetromino = null;
	
	protected int currentScore = 0;
	protected Random randomGenerator = new Random();
	
	protected boolean paused = false;
	protected boolean running = false;
	
	protected BoardObserver observer = null;
		
	public void newGame() {
		currentScore = 0;
		running = true;
		
		for (int i = 0; i < HEIGHT; i++) {
			for (int j = 0; j < WIDTH; j++) {
				board[i][j] = Type.NONE;
			}
		}
		
		newTetromino();
	}
	
	public void endGame() {
		running = false;
		
		currentTetromino = null;
		nextTetromino = null;
		
		for (int i = 0; i < HEIGHT; i++) {
			for (int j = 0; j < WIDTH; j++) {
				board[i][j] = Type.NONE;
			}
		}
		
		if (observer != null) {
			observer.onEndGame(this);
		}
	}
	
	protected void newTetromino() {
		// generate current
		if (currentTetromino == null) {
			currentTetromino = createTetromino(generateRandomType());
		} else {
			currentTetromino = nextTetromino;
		}
		
		// translate tetromino to row center
		currentTetromino.move((Board.WIDTH - currentTetromino.getWidth()) / 2, 0);
		
		// generate next
		nextTetromino = createTetromino(generateRandomType());
		
		if (newCollisionTest() != Collision.NONE) {
			endGame();
		}
	}
	
	public void boardLoop() {
		moveTetromino(0, 1);
	}
	
	public void moveTetromino(int dX, int dY) {
		if (paused == true || running == false) {
			return;
		}
		
		Collision collision = moveCollisionTest(dX, dY);
		
		switch (collision) {
		case FLOOR:
		case BLOCK:
			mergeTetromino();
			checkForRows();
			newTetromino();
			break;
		case WALL:
			// do not move
			break;
		case NONE:
			currentTetromino.move(dX, dY);
			break;
		}
	}
	
	public void rotateTetromino() {
		if (paused == true || running == false) {
			return;
		}
		
		Collision collision = rotateCollisionTest();
		
		if (collision == Collision.NONE) {
			currentTetromino.rotate(true);
		}
	}
	
	protected void mergeTetromino() {
		for (int i = 0; i < Tetromino.SIZE; i++) {
			Block block = currentTetromino.get(i);
			board[block.y][block.x]= currentTetromino.getType(); 
		}
	}
	
	protected Collision moveCollisionTest(int dX, int dY) {
		Collision result = Collision.NONE;
		
		currentTetromino.move(dX, dY);
		
		for (int i = 0; i < Tetromino.SIZE; i++) {
			Block block = currentTetromino.get(i);
			
			if (block.x < 0 || block.x > WIDTH - 1) {
				result = Collision.WALL;
				break;
			}
			if (block.y < 0 || block.y > HEIGHT - 1) {
				result = Collision.FLOOR;
				break;
			}
			
			if (board[block.y][block.x] != Type.NONE) {
				result = (dX == 0) ? Collision.BLOCK : Collision.WALL;
				break;
			}
		}
		
		currentTetromino.move(-dX, -dY);
		
		return result;
	}
	
	protected Collision rotateCollisionTest() {
		Collision result = Collision.NONE;
		
		currentTetromino.rotate(true);
		
		for (int i = 0; i < Tetromino.SIZE; i++) {
			Block block = currentTetromino.get(i);
			
			if (block.x < 0 || block.x > WIDTH - 1) {
				result = Collision.WALL;
				break;
			}
			if (block.y < 0 || block.y > HEIGHT - 1) {
				result = Collision.FLOOR;
				break;
			}
			
			if (board[block.y][block.x] != Type.NONE) {
				result = Collision.BLOCK;
				break;
			}
		}
		
		currentTetromino.rotate(false);
		
		return result;
	}
	
	protected Collision newCollisionTest() {
		for (int i = 0; i < Tetromino.SIZE; i++) {
			Block block = currentTetromino.get(i);
			
			if (board[block.y][block.x] != Type.NONE) {
				return Collision.TOP;
			}
		}
		
		return Collision.NONE;
	}
	
	protected void checkForRows() {
		int totalRows = 0;
		
		for (int i = HEIGHT - 1; i >= 0; i--) {
			boolean found = true; // true if there is full row
			
			for (int j = 0; j < WIDTH; j++) {
				if (board[i][j] == Type.NONE) {
					found = false; // empty space found, so no row
					break;
				}
			}
			
			if (found == true) {
				totalRows++;
				removeRow(i);
				i++; // remain on this row (suppress decrementing)
			}
		}
		
		// set new score
		if (totalRows > 0) {
			final int factor = Integer.parseInt(Config.getInstance().get("score_factor"));
			currentScore += totalRows * factor + (totalRows - 1) * factor;
		}
	}
	
	protected void removeRow(int row) {
		for (int i = row; i > 0; i--) {
			for (int j = 0; j < WIDTH; j++) {
				board[i][j] = board[i - 1][j];
			}
		}
	}
	
	// factory method
	protected Tetromino createTetromino(Type type) {
		switch (type) {
		case CYAN:
			return new CyanTetromino();
		case BLUE:
			return new BlueTetromino();
		case ORANGE:
			return new OrangeTetromino();
		case YELLOW:
			return new YellowTetromino();
		case GREEN:
			return new GreenTetromino();
		case PURPLE:
			return new PurpleTetromino();
		case RED:
			return new RedTetromino();
		default:
			return null;
		}
	}
	
	protected Type generateRandomType() {
		Type[] types = Type.values();

		// do not include NONE = 0, only 1-7
		return types[randomGenerator.nextInt(types.length - 1) + 1];
	}
	
	public Type[][] getBoardForRendering() {
		Type[][] result = new Type[HEIGHT][WIDTH];
		
		// copy board
		for (int i = 0; i < HEIGHT; i++) {
			for (int j = 0; j < WIDTH; j++) {
				result[i][j] = board[i][j];
			}
		}
		
		if (currentTetromino != null) {
			// copy current tetromino
			for (int i = 0; i < Tetromino.SIZE; i++) {
				Block block = currentTetromino.get(i);
				result[block.y][block.x]= currentTetromino.getType(); 
			}
		}
		
		return result;
	}
	
	public Tetromino getNextTetromino() {
		return nextTetromino;
	}
	
	public int getCurrentScore() {
		return currentScore;
	}
	
	public void togglePause() {
		paused ^= true;
		
		/*if (observer != null) {
			if (paused == true) {
				observer.onPauseGame(this);
			} else {
				observer.onResumeGame(this);
			}
		}*/
	}
	
	public void setBoardOserver(BoardObserver o) {
		observer = o;
	}
	
	public interface BoardObserver {
		public void onEndGame(Board board);
		//public void onPauseGame(Board board);
		//public void onResumeGame(Board board);
	}
}
